package com.xzx.redisstudy.seckill;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;

import java.util.List;
import java.util.Random;

/**
 * 秒杀场景
 *
 * @author xinzhixuan
 * @version 1.0
 * @date 2021-05-23 8:27 下午
 */
public class SecKillDemo {
    private static final String PID = "p";
    private static volatile JedisPool jedisPool = null;

    public static void main(String[] args) {

        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                String userid = new Random().nextInt(500000000) + "";
//                doSecKill(userid, PID);
                doSecKillByLuaScript(userid, PID);
            }).start();
        }

    }

    private static String secKillScript = "local userid=KEYS[1];" +
            "local prodid=KEYS[2];" +
            "local qtkey='sk:'..prodid..':qt';" +
            "local usersKey='sk:'..prodid..':user'; " +
            "local userExists=redis.call('sismember', usersKey, userid);" +
            "if tonumber(userExists) == 1 then" +
            "   return 2;" +//用户已经秒杀过了
            "end\n" +
            "local num = redis.call('get', qtkey);" +
            "if tonumber(num) <= 0 then" +
            "   return 0;" +//已经秒杀完了
            "else\n" +
            "   redis.call('decr', qtkey);" +
            "   redis.call('sadd', usersKey, userid);" +
            "end\n" +
            "return 1;";// 秒杀成功


    /**
     * 使用lua脚本来完成秒杀
     * @param uid
     * @param pid
     * @return
     */
    public static boolean doSecKillByLuaScript(String uid, String pid) {
        Jedis jedis=getResource();

        //String sha1=  .secKillScript;
        String sha1=  jedis.scriptLoad(secKillScript);
        Object result= jedis.evalsha(sha1, 2, uid, pid);

        String reString=String.valueOf(result);
        if ("0".equals( reString )  ) {
            System.err.println("已抢空！！");
        }else if("1".equals( reString )  )  {
            System.out.println("抢购成功！！！！");
        }else if("2".equals( reString )  )  {
            System.err.println("该用户已抢过！！");
        }else{
            System.err.println("抢购异常！！");
        }
        jedis.close();
        return true;
    }

    /**
     * 秒杀
     *
     * @param uid 用户id
     * @param pid 产品id
     * @return 秒杀是否成功.
     */
    public static boolean doSecKill(String uid, String pid) {
        //1 uid和prodid非空判断
        if (uid == null || pid == null) {
            return false;
        }

        // 2.获取jedis连接
//        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 使用连接池替换
        Jedis jedis = getResource();

        // 库存的key
        String kcKey = "sk:" + pid + ":qt";
        // 秒杀成功的用户key
        String userKey = "sk:" + pid + ":user";

        // 使用乐观锁，解决超买的问题
        jedis.watch(kcKey);

        // 判断秒杀是否开始
        String kc = jedis.get(kcKey);
        if (kc == null) {
            System.out.println("秒杀暂未开始");
            jedis.close();
            return false;
        }

        // 判断用户是否已经秒杀过
        if (jedis.sismember(userKey, uid)) {
            System.out.println("已经秒杀成功了，不能重复秒杀");
            jedis.close();
            return false;
        }


        // 判断库存是否还有
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("秒杀已经结束");
            jedis.close();
            return false;
        }

        //秒杀，库存减一，秒杀成功的用户加入集合，防止二次秒杀
//        jedis.decr(kcKey);
//        jedis.sadd(userKey, uid);
//        引入事务,可以解决超卖的问题，但是会出现库存可能卖不完的情况，这种场景对先点的不友好，先点的没抢到，后点的抢到了，
        // 这种是因为没有原子性引起的，用lua解决库存遗留问题
        Transaction multi = jedis.multi();
        multi.decr(kcKey);
        multi.sadd(userKey, uid);

        List<Object> list = multi.exec();
        if (list == null || list.size() == 0) {
            System.out.println("秒杀失败");
            jedis.close();
            return false;
        }


        System.out.println("秒杀成功了..");
        // 关闭jedis连接
        jedis.close();
        return true;
    }

    public static Jedis getResource() {
        return getJedisPoolInstance().getResource();
    }

    public static JedisPool getJedisPoolInstance() {
        if (null == jedisPool) {
            synchronized (SecKillDemo.class) {
                if (null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100 * 1000);
                    poolConfig.setBlockWhenExhausted(true);
                    poolConfig.setTestOnBorrow(true);  // ping  PONG

                    jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 60000);
                }
            }
        }
        return jedisPool;
    }

    public static void release(Jedis jedis) {
        if (null != jedis) {
            jedis.close();
        }
    }
}
